
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;

public final class Particle {

    static final double defaultFriction = .1;
    static final double defaultSpeedLimit = 25;
    static final double defaultWaveAmplitude = 5;
    static final double defaultWallBounce = 1;
    static final int defaultSize = 20;
    static volatile int size = defaultSize;
    static volatile double friction = defaultFriction;
    static volatile double speedLimit = defaultSpeedLimit;
    static volatile double waveAmplitude = defaultWaveAmplitude;
    static volatile double wallBounce = defaultWallBounce;
    public final AtomicReference<Vector> velocity;
    public final AtomicReference<Rectangle> shape;
    private final Color color;
    private final AtomicInteger round = new AtomicInteger(1);
    private final Random rng = new Random();

    public Particle(int x, int y, double i, double j) {
        shape = new AtomicReference<Rectangle>(new Rectangle(x, y, size, size));
        velocity = new AtomicReference<Vector>(new Vector(i, j));
        color = new Color(rng.nextFloat(), rng.nextFloat(), rng.nextFloat());
    }

    public boolean move() {
        Vector lv = velocity.get();
        Rectangle ls = shape.get();
        return shape.compareAndSet(ls, new Rectangle((int) (ls.x + lv.i), (int) (ls.y + lv.j), size, size));
    }

    public boolean updateVelocity() {
        Vector tempV = velocity.get();
        Rectangle tempS = shape.get();
        return compareAndSetVelocity(tempV, new Vector(getVelocity(tempS.x, tempV.i), getVelocity(tempS.y, tempV.j)));
    }

    private double getVelocity(double position, double velocity) {
        if (position <= size) velocity = Particle.wallBounce + Math.abs(velocity);
        else if (position >= ParticleApplet.size - size) velocity = -Math.abs(velocity) - Particle.wallBounce;
        return velocity + Particle.waveAmplitude * Math.sin(position);
    }

    public boolean compareAndSetVelocity(Vector expect, Vector update) {
        return velocity.compareAndSet(expect, new Vector(applySpeedLimit(applyFriction(update.i)), applySpeedLimit(applyFriction(update.j))));
    }

    public void setVelocity(double i, double j) {
        velocity.set(new Vector(applySpeedLimit(applyFriction(i)), applySpeedLimit(applyFriction(j))));
    }

    private double applySpeedLimit(double velocity) {
        return (Math.abs(velocity) > Particle.speedLimit) ? ((velocity > 0) ? Particle.speedLimit : -Particle.speedLimit) : velocity;
    }

    private double applyFriction(double velocity) {
        return (Math.abs(velocity) < friction) ? 0 : ((velocity > 0) ? (velocity - friction) : (velocity + friction));
    }

    public boolean hit() {
        round.getAndIncrement();
        return move();
    }

    public void draw(Graphics g) {
        g.setColor(color);
        Rectangle temp = shape.get();
        int roundness = round.get();
        g.fillRoundRect(temp.x, temp.y, size, size, roundness, roundness);
    }
}
